package de.koller.oceanFramework.math;

/**
 * 
 * @author Fabian Koller
 */
public class Vec3 implements ROVec3 {

	private double x = 0;
	private double y = 0;
	private double z = 0;

	/**
	 * 
	 * @param x
	 * @param y
	 * @param z
	 */
	public Vec3( double x, double y, double z ) {
		this.x = x;
		this.y = y;
		this.z = z;
	}

	/**
	 * 
	 * @param vec
	 */
	public Vec3( ROVec3 vec ) {
		this( vec.getX(), vec.getY(), vec.getZ() );
	}

	/**
	 * 
	 * @param xy
	 * @param z
	 */
	public Vec3( ROVec xy, double z ) {
		this( xy.getX(), xy.getY(), z );
	}

	/**
	 * 
	 */
	public Vec3() {
		this( 0, 0, 0 );
	}

	/** returns a - b
	 * @param a 
	 * @param b 
	 * @return 
	 */
	public static Vec3 difVec( Vec3 a, Vec3 b ) {
		Vec3 v = new Vec3( a );
		v.subtract( b );
		return v;
	}
	
	/** Gibt einen neuen Vec3 zurück der nur die Achsen besitzt dessen Flag gesetzt ist.
	 * @param x 
	 * @param z
	 * @param y 
	 * @return  
	 */
	public Vec3 exclude( boolean x, boolean y, boolean z ) {
		Vec3 v = new Vec3( this );
		if( !x ) v.setX( 0 );
		if( !y ) v.setY( 0 );
		if( !z ) v.setZ( 0 );
		return v;
	}
	
	/**
	 * 
	 * @param x
	 * @param y
	 * @param z
	 */
	public void set( double x, double y, double z ) {
		this.x = x;
		this.y = y;
		this.z = z;
	}

	/** Carefui! Slow ... 2 ObjectCreations!
	 * @param rx
	 * @param ry  
	 */
	@Deprecated
	public void setDirection( double rx, double ry ) {
		double l = this.getLength();
		
		Vec vx = new Vec( 1, 0 );
		Vec vy = new Vec( 1, 0 );
		vx.setDirection( rx );
		vy.setDirection( ry );
		
		this.setX( vx.getX() );
		this.setY( vx.getY() + vy.getY() );
		this.setZ( vy.getX() );
		
		this.setLength( l );
	}
	
	/**
	 * 
	 * @param xy
	 * @param z
	 */
	public void set( ROVec xy, double z ) {
		this.set( xy.getX(), xy.getY(), z );
	}

	/**
	 * 
	 * @param v
	 */
	public void set( ROVec3 v ) {
		this.set( v.getX(), v.getY(), v.getZ() );
	}

	/**
	 * 
	 * @param vec
	 * @return
	 */
	public Vec3 add( ROVec3 vec ) {
		this.add( vec.getX(), vec.getY(), vec.getZ() );
		return this;
	}

	/**
	 * 
	 * @param x
	 * @param y
	 * @param z
	 * @return
	 */
	public Vec3 add( double x, double y, double z ) {
		this.x += x;
		this.y += y;
		this.z += z;
		return this;
	}

	/**
	 * 
	 * @param v
	 * @return
	 */
	public Vec3 subtract( ROVec3 v ) {
		this.subtract( v.getX(), v.getY(), v.getZ() );
		return this;
	}

	/**
	 * 
	 * @param x
	 * @param y
	 * @param z
	 * @return
	 */
	public Vec3 subtract( double x, double y, double z ) {
		this.x -= x;
		this.y -= y;
		this.z -= z;
		return this;
	}

	/**
	 * 
	 * @param dir
	 */
	public void setDirectionXY( double dir ) {
		Vec v = this.getXY().newInstance();
		v.setDirection( dir );
		this.setXY( v );
	}

	/**
	 * 
	 * @param xy
	 * @param z
	 */
	public void add( ROVec xy, double z ) {
		add( xy.getX(), xy.getY(), z );
	}

	/**
	 * 
	 * @param xy
	 * @param z
	 * @return
	 */
	public Vec3 subtract( ROVec xy, double z ) {
		subtract( xy.getX(), xy.getY(), z );
		return this;
	}

	/**
	 * 
	 * @param xy
	 */
	public void setXY( ROVec xy ) {
		this.x = xy.getX();
		this.y = xy.getY();
	}

	/**
	 * 
	 * @param x
	 * @param y
	 * @param z
	 */
	public void translate( double x, double y, double z ) {
		this.x += x;
		this.y += y;
		this.z += z;
	}
	
	/**
	 * 
	 * @return
	 */
	@Override
	public ROVec getXY() {
		return new Vec( x, y );
	}

	/**
	 * 
	 * @return
	 */
	@Override
	public double getX() {
		return x;
	}

	/**
	 * 
	 * @param x
	 */
	public void setX(double x) {
		this.x = x;
	}

	/**
	 * 
	 * @return
	 */
	@Override
	public double getY() {
		return y;
	}

	/**
	 * 
	 * @param y
	 */
	public void setY(double y) {
		this.y = y;
	}

	/**
	 * 
	 * @return
	 */
	@Override
	public double getZ() {
		return z;
	}

	/**
	 * 
	 * @param z
	 */
	public void setZ(double z) {
		this.z = z;
	}

	/**
	 * 
	 * @return
	 */
	@Override
	public double getLength() {
		return Math.sqrt( x*x + y*y + z*z );
	}
	
	/**
	 * 
	 * @return
	 */
	public double getLengthSquare() {
		return x*x + y*y + z*z;
	}

	/**
	 * 
	 * @return
	 */
	public Vec3 normalize() {
		if( x == 0 && y == 0 && z == 0 )
			return this;
		
		double l = this.getLength();
		x /= l;
		y /= l;
		z /= l;
		return this;
	}

	/**
	 * 
	 * @param v
	 * @return
	 */
	@Override
	public double distance( ROVec3 v ) {
		Vec3 t = new Vec3( this );
		t.subtract( v );
		return t.getLength();
	}

	@Override
	public String toString() {
		return "Vec3[ " + x + ", " + y + ", " + z + "]";
	}

	/**
	 * 
	 * @return
	 */
	@Override
	public float getXf() {
		return (float)x;
	}
	
	/**
	 * 
	 * @return
	 */
	@Override
	public float getYf() {
		return (float)y;
	}
	
	/**
	 * 
	 * @return
	 */
	@Override
	public float getZf() {
		return (float)z;
	}

	/**
	 * 
	 * @return
	 */
	public Vec3 newInstance() {
		return new Vec3( this );
	}
	
	/**
	 * 
	 * @param length
	 */
	public void setLength( double length ) {
		this.normalize();
		this.x *= length;
		this.y *= length;
		this.z *= length;
	}

	public double dot( Vec3 other ) {
		return IntersectionUtil.dot( this, other );
	}
	
	public Vec3 cross( Vec3 other ) {
		return IntersectionUtil.cross( this, other );
	}
	
	public double angleBetween( Vec3 other ) {
		Vec3 a = this.newInstance().normalize();
		Vec3 b = other.newInstance().normalize();
		return Math.acos( a.dot( b ) );
	}
	
	@Override
	public boolean equals( Object obj ) {
		if( obj == null ) {
			return false;
		}
		if( getClass() != obj.getClass() ) {
			return false;
		}
		final Vec3 other = ( Vec3 ) obj;
		if( Double.doubleToLongBits( this.x ) != Double.doubleToLongBits( other.x ) ) {
			return false;
		}
		if( Double.doubleToLongBits( this.y ) != Double.doubleToLongBits( other.y ) ) {
			return false;
		}
		if( Double.doubleToLongBits( this.z ) != Double.doubleToLongBits( other.z ) ) {
			return false;
		}
		return true;
	}

	@Override
	public int hashCode() {
		int hash = 7;
		hash = 41 * hash + ( int ) ( Double.doubleToLongBits( this.x ) ^ ( Double.doubleToLongBits( this.x ) >>> 32 ) );
		hash = 41 * hash + ( int ) ( Double.doubleToLongBits( this.y ) ^ ( Double.doubleToLongBits( this.y ) >>> 32 ) );
		hash = 41 * hash + ( int ) ( Double.doubleToLongBits( this.z ) ^ ( Double.doubleToLongBits( this.z ) >>> 32 ) );
		return hash;
	}
}
